package pl.enigmatic.math;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map.Entry;
import java.util.Vector;


public abstract class OrderedMap<K, V, L extends List<K>> extends HashMap<K, V> {

	/** */
	private static final long serialVersionUID = 1L;

	public static class VectorImpl<K, V> extends OrderedMap<K, V, Vector<K>> {

		/** */
		private static final long serialVersionUID = 1L;

		public VectorImpl() {
			super(new Vector<K>());
		}
	}

	public static class ArrayListImpl<K, V> extends OrderedMap<K, V, ArrayList<K>> {

		/** */
		private static final long serialVersionUID = 1L;

		public ArrayListImpl() {
			super(new ArrayList<K>());
		}
	}

	private final L list;

	protected OrderedMap(final L list) {
		this.list = list;
	}

	@Override
	public void clear() {
		list.clear();
		super.clear();
	}

	@Override
	public V put(final K key, final V value) {
		if (!list.contains(key)) {
			list.add(key);
		}
		return super.put(key, value);
	}

	public V put(final Entry<? extends K, ? extends V> e) {
		return put(e.getKey(), e.getValue());
	}

	@SuppressWarnings("unchecked")
	public void putAll(final OrderedMap<? extends K, ? extends V, ?> map) {
		final Iterator<?> iter = map.entriesIterator();
		while (iter.hasNext()) {
			put((java.util.Map.Entry<? extends K, ? extends V>) iter.next());
		}
	}

	@Override
	public V remove(final Object key) {
		list.remove(key);
		return super.remove(key);
	}

	public void put(final int index, final K key, final V val) {
		if (list.contains(key)) {
			list.set(index, key);
		} else {
			list.add(index, key);
		}
		put(key, val);
	}

	public boolean putAll(final int index, final HashMap<? extends K, ? extends V> map) {
		int i = index;
		for (final Entry<? extends K, ? extends V> e : map.entrySet()) {
			put(i++, e.getKey(), e.getValue());
		}
		return i > index;
	}

	public boolean containsAll(final Collection<?> keys) {
		return list.containsAll(keys);
	}

	public V get(final int index) {
		return get(list.get(index));
	}

	public int indexOfKey(final Object key) {
		return list.indexOf(key);
	}

	public Iterator<K> keysIterator() {
		return new Iterator<K>() {

			private final Iterator<K> backed = list.iterator();
			private K current = null;

			@Override
			public boolean hasNext() {
				return backed.hasNext();
			}

			@Override
			public K next() {
				return current = backed.next();
			}

			@Override
			public void remove() {
				OrderedMap.this.remove(current);
			}
		};
	}

	public Iterator<V> valuesIterator() {
		return new Iterator<V>() {

			private final Iterator<K> backed = keysIterator();

			@Override
			public boolean hasNext() {
				return backed.hasNext();
			}

			@Override
			public V next() {
				return get(backed.next());
			}

			@Override
			public void remove() {
				backed.remove();
			}
		};
	}

	public Iterator<Entry<K, V>> entriesIterator() {
		return new Iterator<Entry<K, V>>() {

			private final Iterator<K> backed = keysIterator();

			@Override
			public boolean hasNext() {
				return backed.hasNext();
			}

			@Override
			public Entry<K, V> next() {
				return new Entry<K, V>() {

					private final K key = backed.next();

					@Override
					public K getKey() {
						return key;
					}

					@Override
					public V getValue() {
						return get(key);
					}

					@Override
					public V setValue(final V value) {
						return put(key, value);
					}
				};
			}

			@Override
			public void remove() {
				backed.remove();
			}
		};
	}

	public int lastIndexOfKey(final Object key) {
		return list.lastIndexOf(key);
	}

	public ListIterator<K> listIterator() {
		return list.listIterator();
	}

	public ListIterator<K> listIterator(final int index) {
		return list.listIterator(index);
	}

	public V remove(final int index) {
		return super.remove(list.remove(index));
	}

	public boolean removeAll(final Collection<K> keys) {
		boolean res = false;
		for (final K key : keys) {
			res = res || list.contains(key);
			remove(key);
		}
		return res;
	}

	public boolean retainAll(final Collection<K> keys) {
		final Collection<K> remove = new HashSet<K>();
		for (final K key : list) {
			if (!keys.contains(key)) {
				remove.add(key);
			}
		}
		return removeAll(remove);
	}

	public V set(final int index, final V val) {
		return put(list.get(index), val);
	}

	public List<K> subList(final int fromIndex, final int toIndex) {
		return list.subList(fromIndex, toIndex);
	}

	public void sort(final Comparator<? super K> comparator) {
		Collections.sort(list, comparator);
	}

}
